package com.tj.bigdata.syncblock.strategy;

import com.tj.bigdata.syncblock.config.Web3jConfig;
import com.tj.bigdata.syncblock.constant.ErrorStat;
import com.tj.bigdata.syncblock.constant.IndexConstant;
import com.tj.bigdata.syncblock.entity.ReceiptLog;
import com.tj.bigdata.syncblock.entity.Transactions;
import com.tj.bigdata.syncblock.exception.EsException;
import com.tj.bigdata.syncblock.mapper.ErrorBlockMapper;
import com.tj.bigdata.syncblock.pojo.ErrorBlockPo;
import com.tj.bigdata.syncblock.service.EsUpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.core.DefaultBlockParameter;
import org.web3j.protocol.core.methods.response.EthBlock;
import org.web3j.protocol.core.methods.response.TransactionReceipt;

import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 无需创建block的vo对象
 * 指定区块间距
 *
 * @author guoch
 */
@Slf4j
@Service
public class We3jNoArgsImpl implements SyncBlockStrategy {

    @Autowired
    private EsUpService esUpService;

    @Autowired
    private ThreadPoolExecutor executorService;

    @Autowired
    private Web3jConfig web3jConfig;

    @Autowired
    private ErrorBlockMapper errorBlockMapper;

    @Value("${spring.syncblock.begin}")
    private String begin;

    @Override
    public void batchBlockchain() {
        Web3j web3 = web3jConfig.getWeb3j();
        log.debug("连接以太坊成功");
        BigInteger end = new BigInteger(begin).add(new BigInteger(IndexConstant.STEP_SIZE_MARK_50W));
        log.info("开始节点是 ====》 {} 结束节点是 ====》 {}", begin, end);
        web3.replayPastBlocksFlowable(DefaultBlockParameter.valueOf(new BigInteger(begin)), DefaultBlockParameter.valueOf(end), true)
                .doOnError(e -> log.error("on error:{}", e.getMessage()))
                .subscribe(this::executeBlock);
    }

    @Override
    public void batchBlockchain(BigInteger begin, BigInteger end) {

    }

    public void execute(Runnable r) {
        executorService.execute(r);
    }

    public void executeBlock(EthBlock block) {

        execute(() -> {
            // 获取到所需的块信息
            EthBlock.Block ethBlock = block.getBlock();
            // transaction信息获取
            executeTransaction(ethBlock.getTransactions(),ethBlock.getTimestamp());
            try {
                Boolean blockUoRes = esUpService.blockInfoUp(ethBlock);
                if (!blockUoRes) {
                    Boolean blockUoRes1 = esUpService.blockInfoUp(ethBlock);
                    if (!blockUoRes1){
                        ErrorBlockPo errorBlockPo = new ErrorBlockPo();
                        errorBlockPo.setBlockNum(ethBlock.getNumber()).setPartitionIndex(begin).setErrorType(ErrorStat.BLOCK_ERROR.getErrorCode());
                        errorBlockMapper.insertSelective(errorBlockPo);
                        throw new EsException(ethBlock.getNumber() + "区块信息上传ES失败,retry..");
                    }
                }
                log.info("==========区块信息上传ES成功 {}=======", ethBlock.getNumber());
            } catch (IOException e) {
                e.printStackTrace();
            }

        });
    }

    public void executeTransaction(List<EthBlock.TransactionResult> transactions,BigInteger timeStamp) {
        if (transactions.size() == 0) {
            return;
        }
        try {
            Web3j web3j = web3jConfig.getWeb3j();
            List<Transactions> transactionsList = new ArrayList<>();
            Transactions transactionss;
            for (EthBlock.TransactionResult<EthBlock.TransactionObject> transactionResult : transactions) {
                EthBlock.TransactionObject transaction = transactionResult.get();
                transactionss = new Transactions();
                transactionss.setBlockHash(transaction.getBlockHash()).setBlockNumber(transaction.getBlockNumber()).setTimeStamp(timeStamp)
                        .setFrom(transaction.getFrom()).setTo(transaction.getTo()).setGas(transaction.getGas()).setGasPrice(transaction.getGasPrice())
                        .setHash(transaction.getHash()).setTransactionIndex(transaction.getTransactionIndex()).setInput(transaction.getInput()).setNonce(transaction.getNonce())
                        .setValue(transaction.getValue());
                // TransactionReceipt 数据获取
                TransactionReceipt receipt = web3j.ethGetTransactionReceipt(transaction.getHash()).send().getResult();
                if (receipt != null) {
                    BeanUtils.copyProperties(receipt, transactionss);
                    ArrayList<ReceiptLog> receiptLogs = new ArrayList<>();
                    receipt.getLogs().forEach(log1 -> {
                        ReceiptLog receiptLog = new ReceiptLog();
                        receiptLog.setRemoved(log1.isRemoved()).setLogIndex(log1.getLogIndex())
                                .setAddress(log1.getAddress()).setData(log1.getData()).setTopics(log1.getTopics());
                        receiptLogs.add(receiptLog);
                    });
                    transactionss.setLogs(receiptLogs);
                }
                transactionsList.add(transactionss);
            }
            Boolean transactionUpRes = esUpService.transactionInfoUp(transactionsList);
            if (!transactionUpRes) {
                Boolean transactionUpRes1 = esUpService.transactionInfoUp(transactionsList);
                if (!transactionUpRes1){
                    ErrorBlockPo errorBlockPo = new ErrorBlockPo();
                    errorBlockPo.setBlockNum(transactionsList.get(0).getBlockNumber()).setPartitionIndex(begin).setErrorType(ErrorStat.TRANSACTION_ERROR.getErrorCode());
                    errorBlockMapper.insertSelective(errorBlockPo);
                    throw new EsException(transactionsList.get(0).getBlockNumber() + "区块交易信息上传ES失败,retry..");
                }
            }
            log.info("==========交易信息上传ES成功 {}=======", transactionsList.get(0).getBlockNumber());
        } catch (IOException e) {
            log.error("transaction input error, msg:{}", e.getMessage());
        }
    }
}
